﻿using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ProtoGame.GameLib.Entities;
using ProtoGame.GameLib.GameComponents.Debug;
using System;

namespace ProtoGame.GameLib
{
    public class EntityManager : DrawableGameComponent
    {
        SpriteBatch spriteBatch;
        ScreenStateEffects screenState;
        Matrix transform = Matrix.Identity;

        List<Entity> entities = new List<Entity>();
        List<Tilemap> tilemaps = new List<Tilemap>();

        // Buffers são necessários para poder adicionar itens com o jogo rodando,
        // mas sem estragar a lógica de execução (um item novo só entra no turno
        // seguinte).
        List<Entity> entitiesAddBuffer = new List<Entity>();
        List<Tilemap> tilemapsAddBuffer = new List<Tilemap>();

        public EntityManager(Game game)
            : base(game)
        {
            screenState = new ScreenStateEffects();
            screenState.StartingTime = TimeSpan.FromSeconds(0.4);
            screenState.EndingTime = TimeSpan.FromSeconds(0.1);
        }

        public GraphicEntity FrameFocus { get; set; }
        public Rectangle WorldBounds { get; set; }

        public void AddEntity(Entity entity)
        {
            Tilemap tilemap = entity as Tilemap;
            if (tilemap != null)
                AddEntity(tilemap);
            else
                entitiesAddBuffer.Add(entity);
        }

        public void AddEntity(Tilemap entity)
        {
            tilemapsAddBuffer.Add(entity);
        }

        public void WakeUp()
        {
            screenState.WakeUp();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            base.LoadContent();
        }

        public override void Update(GameTime gameTime)
        {
            IntegrateAddBuffers();

            screenState.Update(gameTime.ElapsedGameTime);
            if (screenState.IsActive)
                UpdateEntities(gameTime);
            else
                UpdateEntitiesFade();

            PositionFrameFocus();

            /*DebugComponent debug = Game.Services.GetService(typeof(DebugComponent)) as DebugComponent;
            debug.FixedMessage = FrameFocus.ToString();*/
        }

        private void UpdateEntitiesFade()
        {
            foreach (Tilemap tilemap in tilemaps)
                tilemap.Fade = screenState.Fade;

            foreach (Entity entity in entities)
            {
                GraphicEntity ge = entity as GraphicEntity;
                if (ge != null)
                    ge.Fade = screenState.Fade;
            }
        }

        private void UpdateEntities(GameTime gameTime)
        {
            foreach (Tilemap tilemap in tilemaps)
            {
                if (tilemap.Alive)
                {
                    tilemap.Update(gameTime);
                    tilemap.Fade = screenState.Fade;
                }
            }

            foreach (Entity entity in entities)
            {
                if (entity.Alive)
                {
                    entity.Update(gameTime);

                    foreach (Tilemap tilemap in tilemaps)
                        tilemap.CheckTileCollision(entity);

                    foreach (Entity collidable in entities)
                    {
                        if (!collidable.Equals(entity) && collidable.Alive)
                        {
                            if (collidable.DoCheckCollision(entity))
                            {
                                collidable.DoCollision(entity.CollisionType);
                                entity.DoCollision(collidable.CollisionType);
                            }
                        }
                    }
                }
            }
        }

        private void IntegrateAddBuffers()
        {
            foreach(Entity entity in entitiesAddBuffer)
            {
                entities.Add(entity);
            }
            entitiesAddBuffer.Clear();

            foreach (Tilemap tilemap in tilemapsAddBuffer)
                tilemaps.Add(tilemap);
            tilemapsAddBuffer.Clear();
        }

        /// <summary>
        /// Calcula a translação global necessária para posicionar o sprite em foco ao centro da tela.
        /// </summary>
        private void PositionFrameFocus()
        {
            if (FrameFocus != null)
            {
                Point frameCenter = FrameFocus.Bounds.Center;

                // Calcula o ponto a ser posicionado ao centro da tela, evitando de sair dos limites do tabuleiro
                frameCenter.X = (int)MathHelper.Clamp(frameCenter.X, Game.GraphicsDevice.Viewport.Width / 2, WorldBounds.Width - Game.GraphicsDevice.Viewport.Width / 2);
                frameCenter.Y = (int)MathHelper.Clamp(frameCenter.Y, Game.GraphicsDevice.Viewport.Height / 2, WorldBounds.Height - Game.GraphicsDevice.Viewport.Height / 2);

                Matrix.CreateTranslation(
                    -(frameCenter.X - Game.GraphicsDevice.Viewport.Width / 2),
                    -(frameCenter.Y - Game.GraphicsDevice.Viewport.Height / 2),
                    0,
                    out transform);
            }
        }

        public override void Draw(GameTime gameTime)
        {
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.SaveState, transform);

            foreach (Tilemap tilemap in tilemaps)
                if (tilemap.Visible)
                    tilemap.Draw(spriteBatch);

            foreach (Entity entity in entities)
            {
                GraphicEntity ge = entity as GraphicEntity;
                if (ge != null)
                    if (ge.Visible)
                        ge.Draw(spriteBatch);
            }

            spriteBatch.End();
        }
    }
}
